package com.zhy.view;

import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.Text;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.text.Layout;
import ohos.agp.text.SimpleTextLayout;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.app.Context;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by zhy on 15/8/20.
 */
public class MixtureTextView extends DependentLayout implements Component.DrawTask {
    /**
     * Text color
     */
    public static final String TEXT_COLOR = "textColor";
    /**
     * Text size
     */
    public static final String TEXT_SIZE = "textSize";
    /**
     * Text
     */
    public static final String TEXT = "text";
    private Layout layout = null;

    /**
     * 行高
     */
    private int mLineHeight;

    private Color mTextColor = Color.BLACK;
    private int mTextSize = sp2px(14);
    private String mText;

    private int mLineSpace;

    private Paint mTextPaint;

    private List<List<Rect>> mDestRects = new ArrayList<List<Rect>>();
    private List<Integer> mCorYs = null;
    private HashSet<Integer> mCorYHashes = new HashSet<Integer>();

    private int mMaxHeight;
    private int mHeightMeasureSpec;
    private int mOriginHeightMeasureMode;
    private int mHeightReMeasureSpec;
    private boolean mNeedReMeasure;
    private boolean mNeedRenderText;

    private int mMinHeight;

    private Map<Integer, Point> mViewBounds = new HashMap<Integer, Point>();


    public MixtureTextView(Context context, AttrSet attrs) {
        super(context, attrs);

        readAttrs(context, attrs);

        // just for text
        if (mText == null) {
            mText = context.getString(ResourceTable.String_text);
        }

        // get text
        if (!isEmpty(mText)) {
            mNeedRenderText = true;
        }

        if (!mNeedRenderText) {
            return;
        }

        mTextPaint = new Paint();
        mTextPaint.setDither(true);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(mTextColor);
        mTextPaint.setMultipleLine(true);
        addDrawTask(this);
    }

    private void readAttrs(Context context, AttrSet attrs) {
        if (attrs != null) {
            mText = attrs.getAttr(TEXT).isPresent()
                    ? attrs.getAttr(TEXT).get().getStringValue() : "";
            mTextSize = attrs.getAttr(TEXT_SIZE).isPresent()
                    ? attrs.getAttr(TEXT_SIZE).get().getDimensionValue() : mTextSize;
            mTextColor = attrs.getAttr(TEXT_COLOR).isPresent()
                    ? attrs.getAttr(TEXT_COLOR).get().getColorValue() : Color.BLACK;
        }
    }

    /**
     * onMeasure
     * @param widthMeasureSpec widthMeasureSpec
     * @param heightMeasureSpec heightMeasureSpec
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!mNeedRenderText) {
            super.estimateSize(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        mHeightMeasureSpec = heightMeasureSpec;
        mTextPaint.setTextSize(mTextSize);

        cacuLineHeight();

        if (mNeedReMeasure) {
            super.estimateSize(widthMeasureSpec, mHeightReMeasureSpec);
        } else {
            super.estimateSize(widthMeasureSpec, heightMeasureSpec);
        }
    }

    private void cacuLineHeight() {
        layout = new SimpleTextLayout("MixtureTextView", mTextPaint, new Rect(), 0);
        mLineHeight = (int) (layout.getBottom(0) - layout.getTop(0));
    }

    private boolean mFirstInLayout = true;

    /**
     * onLayout
     */
    protected void onLayout() {
        if (mFirstInLayout) {
            mOriginHeightMeasureMode = EstimateSpec.getMode(mHeightMeasureSpec);
            mFirstInLayout = false;
            mMinHeight = getEstimatedHeight();
        }
        if (!mNeedRenderText) {
            return;
        }
        getAllYCors();
    }


    private boolean tryDraw(Canvas canvas) {
        boolean kidding = canvas == null;
        int lineHeight = mLineHeight;
        List<List<Rect>> destRects = mDestRects;

        int start = 0;
        int lineSum = 0;
        int fullSize = mText.length();
        for (int i = 0; i < destRects.size(); i++) {
            List<Rect> rs = destRects.get(i);
            if(rs.isEmpty()) {
                continue;
            }
            Rect rect = rs.get(0);
            int rectWidth = rect.getWidth();
            int rectHeight = rect.getHeight();

            layout = generateLayout(mText.substring(start), rectWidth);
            int lineCount = rectHeight / lineHeight;
            lineCount = layout.getLineCount() < lineCount ? layout.getLineCount() : lineCount;

            if (!kidding) {
                canvas.save();
                canvas.translate(rect.left, rect.top);
                canvas.clipRect(0, 0, rect.getWidth(), layout.getBottom(lineCount - 1) - layout.getTop(0));
                layout.drawText(canvas);
                canvas.restore();
            }
            start += layout.getEndCharIndex(lineCount - 1);
            lineSum += lineCount;
            if (start >= fullSize) {
                break;
            }
        }

        if (kidding) {
            mMaxHeight += lineSum * lineHeight;
            if ((mMaxHeight > mMinHeight && getHeight() != mMaxHeight)
                    && mOriginHeightMeasureMode != EstimateSpec.PRECISE) {
                mHeightReMeasureSpec = EstimateSpec.getSizeWithMode(mMaxHeight, EstimateSpec.PRECISE);
                mNeedReMeasure = true;
                postLayout();
                return true;
            }
        }
        return false;
    }

    /**
     * 获取所有的y坐标
     */
    private void getAllYCors() {
        int lineHeight = mLineHeight;

        Set<Integer> corYSet = mCorYHashes;
        corYSet.clear();
        mViewBounds.clear();

        // 获得所有的y轴坐标
        int cCount = getChildCount();
        for (int i = 0; i < cCount; i++) {
            Component view = getComponentAt(i);
            if (view.getVisibility() == Component.HIDE) {
                continue;
            }

            int top = view.getTop();
            int availableTop = top - getPaddingTop();
            availableTop = availableTop / lineHeight * lineHeight;
            top = availableTop + getPaddingTop();

            corYSet.add(top);

            int bottom = view.getBottom();
            int availableBottom = bottom - getPaddingTop();
            availableBottom = availableBottom % lineHeight == 0 ?
                    availableBottom : (availableBottom / lineHeight + 1) * lineHeight;
            bottom = availableBottom + getPaddingTop();

            corYSet.add(bottom);

            mViewBounds.put(i, new Point(top, bottom));
        }
        corYSet.add(getPaddingTop());

        if (mOriginHeightMeasureMode == EstimateSpec.PRECISE) {
            corYSet.add(getHeight());
        } else {
            corYSet.add(Integer.MAX_VALUE);
        }
        // 排序
        List<Integer> corYs = new ArrayList<Integer>(corYSet);
        Collections.sort(corYs);

        mCorYs = corYs;
    }

    /**
     * dispatchDraw
     * @param canvas canvas
     */
    protected void dispatchDraw(Canvas canvas) {
        mMaxHeight = getPaddingBottom() + getPaddingTop();
        initAllNeedRenderRect();
        boolean skipDraw = tryDraw(null);
        if (skipDraw) {
            return;
        }
        tryDraw(canvas);
    }

    private void initAllNeedRenderRect() {
        int lineHeight = mLineHeight;
        List<List<Rect>> destRects = this.mDestRects;
        List<Integer> corYs = mCorYs;
        destRects.clear();

        int minLeft = getPaddingLeft();
        int maxRight = getWidth() - getPaddingRight();

        // find rect between y1 and y2
        List<Rect> viewRectBetween2Y = null;
        for (int i = 0; i < corYs.size() - 1; i++) {
            int y1 = corYs.get(i);
            int y2 = corYs.get(i + 1);

            viewRectBetween2Y = new ArrayList<Rect>();

            List<Rect> rs = caculateViewYBetween(y1, y2);

            Rect leftFirst = null;
            switch (rs.size()) {
                case 0:
                    viewRectBetween2Y.add(new Rect(minLeft, y1, maxRight, y2));
                    break;
                case 1:
                    leftFirst = rs.get(0);
                    // 添加第一个Rect
                    tryAddFirst(leftFirst, viewRectBetween2Y, y1, y2, minLeft);
                    tryAddLast(leftFirst, viewRectBetween2Y, y1, y2, maxRight);
                    break;
                default:
                    // add first
                    leftFirst = rs.get(0);
                    tryAddFirst(leftFirst, viewRectBetween2Y, y1, y2, minLeft);
                    // add mid
                    for (int j = 0; j < rs.size() - 1; j++) {
                        Rect ra = rs.get(j);
                        Rect rb = rs.get(j + 1);

                        if (ra.right < rb.left) {
                            viewRectBetween2Y.add(new Rect(ra.right, y1, rb.left, y2));
                        }
                    }
                    // add last
                    Rect lastRect = rs.get(rs.size() - 1);
                    tryAddLast(lastRect, viewRectBetween2Y, y1, y2, maxRight);
                    break;
            }
            destRects.add(viewRectBetween2Y);
        }

        // split
        List<List<Rect>> bak = new ArrayList<List<Rect>>(destRects);
        int destRectSize = destRects.size();
        int inc = 0; // 索引增量
        for (int i = 0; i < destRectSize; i++) {
            List<Rect> rs = destRects.get(i);
            if (rs.size() > 1) {
                int index = inc + i;
                bak.remove(rs);
                inc--;
                Rect rect1 = rs.get(0);
                int lh = rect1.getHeight() / lineHeight;
                mMaxHeight -= lh * (rs.size() - 1) * lineHeight;

                for (int k = 0; k < lh; k++) {
                    for (int j = 0; j < rs.size(); j++) {
                        inc++;
                        bak.add(index++, Arrays.asList(new Rect(
                                rs.get(j).left,
                                rect1.top + lineHeight * k,
                                rs.get(j).right,
                                rect1.top + lineHeight * k + lineHeight)));
                    }
                }
            }
        }
        mDestRects = bak;
    }

    private void tryAddLast(Rect leftFirst, List<Rect> viewRectBetween2Y, int y1, int y2, int maxRight) {
        if (leftFirst.right < maxRight) {
            viewRectBetween2Y.add(new Rect(leftFirst.right, y1, maxRight, y2));
        }
    }

    private void tryAddFirst(Rect leftFirst, List<Rect> viewRectBetween2Y, int y1, int y2, int minLeft) {
        if (leftFirst.left > minLeft) {
            viewRectBetween2Y.add(new Rect(minLeft, y1, leftFirst.left, y2));
        }
    }

    private SimpleTextLayout generateLayout(String text, int width) {
        return new SimpleTextLayout(text, mTextPaint, new Rect(), width);
    }

    /**
     * setText
     * @param text text
     */
    public void setText(String text) {
        if (isEmpty(text)) {
            mNeedRenderText = false;
            postLayout();
            return;
        }
        mNeedRenderText = true;
        mText = text;
        postLayout();
        invalidate();
    }

    /**
     * setTextColor
     * @param color color
     */
    public void setTextColor(Color color) {
        mTextPaint.setColor(color);
        mTextColor = color;
        invalidate();
    }

    /**
     * setTextSize
     * @param unit unit
     * @param size size
     */
    public void setTextSize(Text.TextSizeType unit, int size) {
        if(size <= 0) {
            return;
        }
        switch (unit) {
            case PX:
                mTextSize = size;
                break;
            case VP:
                mTextSize = dp2px(size);
                break;
            case FP:
                mTextSize = sp2px(size);
                break;
        }
        mTextPaint.setTextSize(mTextSize);
        postLayout();
        invalidate();
    }

    /**
     * setTextSize
     * @param pxSize size
     */
    public void setTextSize(int pxSize) {
        setTextSize(Text.TextSizeType.PX, pxSize);
    }

    /**
     * 计算包含在y1到y2间的矩形区域
     *
     * @param y1 y co-ordinate
     * @param y2 y co-ordinate
     * @return list of rect
     */
    private List<Rect> caculateViewYBetween(int y1, int y2) {
        List<Rect> rs = new ArrayList<>();
        Rect tmp = null;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            Component view = getComponentAt(i);
            Point point = mViewBounds.get(i);
            float top = point.getPointX();
            float bottom = point.getPointY();

            if (top <= y1 && bottom >= y2) {
                tmp = new Rect(view.getLeft(), y1, view.getRight(), y2);
                rs.add(tmp);
            }
        }
        Collections.sort(rs, new Comparator<Rect>() {
            @Override
            public int compare(Rect lhs, Rect rhs) {
                if (lhs.left > rhs.left) {
                    return 1;
                }
                return -1;
            }
        });
        if (rs.size() >= 2) {
            List<Rect> res = new ArrayList<Rect>(rs);
            Rect pre = rs.get(0);
            Rect next = rs.get(1);
            // 合并
            for (int i = 1; i < rs.size(); i++) {
                // if相交
                if (Rect.isIntersect(pre, next)) {
                    int left = Math.min(pre.left, next.left);
                    int right = Math.max(pre.right, next.right);

                    res.remove(pre);
                    res.remove(next);
                    res.add(new Rect(left, y1, right, y2));

                    if (res.size() >= 2) {
                        pre = rs.get(0);
                        next = rs.get(1);
                    } else {
                        break;
                    }
                } else {
                    if ((res.size() - i) >= 2) {
                        pre = next;
                        next = rs.get(i + 1);
                    } else {
                        break;
                    }
                }
            }
            rs = res;
        }
        return rs;
    }

    /**
     * fp2px
     * @param spVal fp2px
     * @return int
     */
    public int sp2px(int spVal) {
        return AttrHelper.fp2px(spVal, getContext());
    }

    /**
     * vp2px
     * @param dpVal vpval
     * @return int
     */
    public int dp2px(int dpVal) {
        return AttrHelper.vp2px(dpVal, getContext());
    }

    /**
     * getTextSize
     * @return size
     */
    public int getTextSize() {
        return mTextSize;
    }

    /**
     * getTextColor
     * @return Color
     */
    public Color getTextColor() {
        return mTextColor;
    }

    /**
     * getText
     * @return string
     */
    public String getText() {
        return mText;
    }

    private boolean isEmpty(String string) {
        return string == null || string.length() == 0;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        int width = EstimateSpec.getSizeWithMode(getWidth(), EstimateSpec.PRECISE);
        int height = EstimateSpec.getSizeWithMode(getHeight(), EstimateSpec.PRECISE);
        onMeasure(width, height);
        onLayout();
        dispatchDraw(canvas);
    }
}
